package com.zrfintech.dao.gen;

import cn.hutool.core.io.FileUtil;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import lombok.extern.java.Log;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.sql.*;
import java.util.List;
import java.util.Locale;
import java.util.Properties;

@Log
public class StubGenerator {
    private static String root;
    private static String pack = "package";
    private static String module = "";
    private static String stubRoot = root + "gen";
    private static String stubPath = root + "gen/" + pack;
    private static String corePath = root + "stub/" + pack;
    private static String jdbcUrl = "jdbc:mysql//";
    private static String jdbcUsername = "";
    private static String jdbcPassword = "";

    private static List<String> noNeedColumnList = Lists.newArrayList("id", "cts", "uts");

    public static void main(String[] args) throws IOException, SQLException, ClassNotFoundException {
        Properties ps = new Properties();
        ps.setProperty("java.pack", "com.zrfintech");
        ps.setProperty("java.module", "");

        root = ps.getProperty("java.root", "dao/src/main");
        pack = ps.getProperty("java.pack", "package");
        module = ps.getProperty("java.module", "");

        stubRoot = root + "/gen";
        stubPath = root + "/gen/" + pack.replace('.', '/');
        corePath = root + "/stub/" + pack.replace('.', '/');

        log.info("module:" + module);
        log.info("module:" + new File(root).getCanonicalPath());
        log.info("start...");
        new StubGenerator().generate(module);
        log.info("success...");

    }

    private void generate(String fore) throws SQLException, ClassNotFoundException, IOException {
        Connection con = getConnection();
        String[] tables = getTables(con, "");
//        String[] tables = new String[]{"mg_menu"};
        for (String table : tables) {
            Column[] columns = getColumns(con, table);

            //domain
            generateDomain(table, columns, fore);
            //daoI
            generateDaoI(table, columns, fore);
            //dao
            generateDao(table, columns, fore);
            // repoI
            generateRepoI(table, columns, fore);
            // repo
            generateRepo(table, columns, fore);
        }

    }

    private void generateDaoI(String table, Column[] columns, String fore) throws IOException {
        String genPath = stubPath + "/" + fore + "/dao";
        String genPack = "package " + pack + "." + fore + ".dao";
        if (StringUtils.isEmpty(fore)) {
            genPack = "package " + pack + ".dao";
        }
        String domainName = upper(table);
        String genName = upper(table) + "DaoI";
        StringBuffer sb = new StringBuffer();
        line(genPack + ";", 0, sb);
        line("", 0, sb);

        line("import com.baomidou.mybatisplus.core.mapper.BaseMapper;", 0, sb);
        if (StringUtils.isEmpty(fore)) {
            line("import " + pack + ".domain." + domainName + ";", 0, sb);
        } else {
            line("import " + pack + "." + fore + ".domain." + domainName + ";", 0, sb);
        }

        line("import org.apache.ibatis.annotations.Insert;", 0, sb);
        line("import org.apache.ibatis.annotations.Options;", 0, sb);


        line("public interface " + genName + " extends BaseMapper<" + domainName + "> {", 0, sb);

        StringBuffer insertSqlStart = new StringBuffer(String.format("@Insert(\"INSERT INTO `%s` (", table));
        StringBuffer insertSqlEnd = new StringBuffer("VALUES (");
        List<String> columnList = Lists.newArrayList();
        List<String> propertyList = Lists.newArrayList();

        for (Column column : columns) {
            if (noNeedColumnList.contains(column.name)) {
                continue;
            }
            columnList.add("`" + column.name + "`");
            propertyList.add("#{" + upperForColumn(column.name) + "}");
        }
        Joiner joiner = Joiner.on(",");
        insertSqlStart.append(joiner.join(columnList));
        insertSqlStart.append(") ");

        insertSqlEnd.append(joiner.join(propertyList));
        insertSqlEnd.append(")");

        line("", 0, sb);
        line(insertSqlStart.append(insertSqlEnd).toString() + "\")", 1, sb);
        line("@Options(useGeneratedKeys = true, keyProperty = \"id\")", 1, sb);
        line("int create(" + domainName + " " + lowerFirstChar(domainName) + ");", 1, sb);

        line("}", 0, sb);
        checkAndSave(sb, genPath + "/" + genName + ".java", "package");
    }

    private void generateRepoI(String table, Column[] columns, String fore) throws IOException {
        String genPath = stubPath + "/" + fore + "/service";
        String genPack = "package " + pack + "." + fore + ".service";
        if (StringUtils.isEmpty(fore)) {
            genPack = "package " + pack + ".service";
        }
        String domainName = upper(table);
        String genName = upper(table) + "RepoI";
        StringBuffer sb = new StringBuffer();
        line(genPack + ";", 0, sb);
        line("", 0, sb);
        line("import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;", 0, sb);
        line("import com.baomidou.mybatisplus.extension.plugins.pagination.Page;", 0, sb);
        line("import com.zrfintech.common.pojo.BasePageEntity;", 0, sb);

        line("import org.springframework.stereotype.Service;", 0, sb);
        line("import org.springframework.beans.factory.annotation.Autowired;", 0, sb);
        line("import java.util.List;", 0, sb);
        if (StringUtils.isNotEmpty(fore)) {
            line("import " + pack + "." + fore + ".domain." + domainName + ";", 0, sb);
            line("import " + pack + "." + fore + ".dao." + domainName + "Dao;", 0, sb);
        } else {
            line("import " + pack + ".domain." + domainName + ";", 0, sb);
            line("import " + pack + ".dao." + domainName + "Dao;", 0, sb);
        }

        line("@Service", 0, sb);
        line("public class " + genName + "{", 0, sb);

        line("", 0, sb);
        line("@Autowired", 1, sb);
        line("protected " + domainName + "Dao dao;", 1, sb);

        line("", 0, sb);
        line("public " + domainName + " getById(Long id) {", 1, sb);
        line("return dao.selectById(id);", 2, sb);
        line("}", 1, sb);

        line("", 0, sb);
        line("public List<" + domainName + "> getAll() {", 1, sb);
        line("return dao.selectList(null);", 2, sb);
        line("}", 1, sb);

        line("", 0, sb);
        line("public int deleteById(Long id) {", 1, sb);
        line("return dao.deleteById(id);", 2, sb);
        line("}", 1, sb);


        line("", 0, sb);
        line("public int create(" + domainName + " item) {", 1, sb);
        line("return dao.create(item);", 2, sb);
        line("}", 1, sb);


        line("", 0, sb);
        line("public int updateById(" + domainName + " item) {", 1, sb);
        line("return dao.updateById(item);", 2, sb);
        line("}", 1, sb);

        line("", 0, sb);
        line("public boolean hasById(Long id) {", 1, sb);
        line("return dao.selectById(id) == null;", 2, sb);
        line("}", 1, sb);


        line("", 0, sb);
        line("public Page<" + domainName + "> pageList(BasePageEntity basePageEntity, QueryWrapper<" + domainName + "> queryWrapper) {", 1, sb);
        line("Page<" + domainName + "> rowPage = new Page(basePageEntity.getPage(), basePageEntity.getPerPage());", 2, sb);
        line("return dao.selectPage(rowPage, queryWrapper);", 2, sb);
        line("}", 1, sb);

        for (Column column : columns) {
            line("", 0, sb);
            line("public static final String " + column.name.toUpperCase(Locale.ROOT) + "_COLUMN = \"" + column.name + "\";", 1, sb);
        }

        line("}", 0, sb);
        checkAndSave(sb, genPath + "/" + genName + ".java", "package");
    }

    private void generateDao(String table, Column[] columns, String fore) throws IOException {
        String genPath = corePath + "/" + fore + "/dao";
        String genPack = "package " + pack + "." + fore + ".dao";
        if (StringUtils.isEmpty(fore)) {
            genPack = "package " + pack + ".dao";
        }
//        String domainName = upper(table);
        String genName = upper(table) + "Dao";
        StringBuffer sb = new StringBuffer();
        line(genPack + ";", 0, sb);
        line("", 0, sb);

//        line("import com.baomidou.mybatisplus.core.mapper.BaseMapper;", 0, sb);
//        line("import " + pack + "." + fore + ".domain." + domainName + ";", 0, sb);


        line("public interface " + genName + " extends " + genName + "I {", 0, sb);


        line("}", 0, sb);
        String fileFullName = genPath + "/" + genName + ".java";
        File file = new File(fileFullName);
        if (file.exists()) {
            return;
        }
        checkAndSave(sb, fileFullName, "package");
    }


    private void generateRepo(String table, Column[] columns, String fore) throws IOException {
        String genPath = corePath + "/" + fore + "/service";
        String genPack = "package " + pack + "." + fore + ".service";

        if (StringUtils.isEmpty(fore)) {
            genPack = "package " + pack + ".service";
        }
        String genName = upper(table) + "Repo";
        StringBuffer sb = new StringBuffer();
        line(genPack + ";", 0, sb);
        line("", 0, sb);

        line("import org.springframework.stereotype.Service;", 0, sb);

        line("@Service", 0, sb);
        line("public class " + genName + " extends " + genName + "I {", 0, sb);


        line("}", 0, sb);
        String fileFullName = genPath + "/" + genName + ".java";
        File file = new File(fileFullName);
        if (file.exists()) {
            return;
        }
        checkAndSave(sb, fileFullName, "package");
    }

    private void generateDomain(String table, Column[] columns, String fore) throws IOException {
        String domainPath = stubPath + "/" + fore + "/domain";
        String domainPack = "package " + pack + "." + fore + ".domain";
        if (StringUtils.isEmpty(fore)) {
            domainPack = "package " + pack + ".domain";
        }
        String domainName = upper(table);

        StringBuffer sb = new StringBuffer();

        line(domainPack + ";", 0, sb);
        line("", 0, sb);
        line("import lombok.Data;", 0, sb);
        line("import java.util.Date;", 0, sb);
        line("import com.baomidou.mybatisplus.annotation.TableName;", 0, sb);


        line("@Data", 0, sb);
        line("@TableName(\"" + table + "\")", 0, sb);

        line("public class " + domainName + " {", 0, sb);
        line("", 0, sb);

        for (Column column : columns) {
            line("private " + column.getType() + " " + upperForColumn(column.name) + ";", 1, sb);
        }
        line("}", 0, sb);
        checkAndSave(sb, domainPath + "/" + domainName + ".java", "package");


    }

    private void checkAndSave(CharSequence content, String path, String split) throws IOException {
        String oldBody = readFile(path);
        if (oldBody != null) {
            oldBody = oldBody.substring(oldBody.indexOf(split));
        }
        if (oldBody == null || !content.toString().endsWith(oldBody)) {
            saveFile(path, content);
        }
    }

    private String readFile(String path) {
        String absolutePath = FileUtil.getAbsolutePath(new File("")) + path;
        File file = new File(absolutePath);
        if (file.exists()) {
            return FileUtil.readUtf8String(absolutePath + path);
        } else {
            return null;
        }
    }

    private void saveFile(String path, CharSequence content) throws IOException {
        new File(path).getParentFile().mkdirs();
        OutputStream out = null;
        try {
            out = new FileOutputStream(path);
            out.write(content.toString().getBytes("utf-8"));
        } finally {
            if (out != null) {
                out.close();
            }
        }
    }

    private Column[] getColumns(Connection con, String table) throws SQLException {

        PreparedStatement ps = null;
        ResultSet set = null;
        List<Column> columns = Lists.newArrayList();
        try {
            ps = con.prepareStatement("desc " + table);
            set = ps.executeQuery();
            while (set.next()) {
                columns.add(new Column(set.getString(1), set.getString(2)));
            }
        } finally {
            close(ps, set);
        }
        return columns.toArray(new Column[columns.size()]);
    }

    private static Connection getConnection() throws SQLException, ClassNotFoundException {
        Class.forName("com.mysql.jdbc.Driver");

        String url = "jdbc:mysql://localhost:3307/sparrow_new";
        String userName = "root";
        String password = "123456";
        Connection connection = DriverManager.getConnection(url, userName, password);
        return connection;
//        return DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword);
    }

    private String[] getTables(Connection con, String fore) throws SQLException {
        PreparedStatement ps = null;
        ResultSet set = null;
        List<String> tables = Lists.newArrayList();
        try {
            ps = con.prepareStatement("show tables");
            set = ps.executeQuery();
            while (set.next()) {
                tables.add(set.getString(1));
            }
        } finally {
            close(ps, set);
        }
        return tables.toArray(new String[tables.size()]);
    }

    private void close(Statement ps, ResultSet rs) {
        close(ps);
        close(rs);
    }

    private void close(Statement ps) {
        try {
            if (ps != null) {
                ps.close();
            }
        } catch (SQLException e) {
            log.warning("preStmt close failed,skip");
        }
    }

    private void close(ResultSet rs) {
        try {
            if (rs != null) {
                rs.close();
            }
        } catch (SQLException e) {
            log.warning("rs close failed,skip");
        }
    }

    private class Column {
        String name;
        String type;

        public Column(String name, String type) {
            this.name = name;
            this.type = type;
        }

        public String getType() {
            return convertType(name, type);
        }


    }

    private String convertType(String name, String type) {
        if (type.contains("char") || type.contains("binary") || type.contains("text")) {
            return "String";
        }
        if (type.contains("tinyint(1)")) {
            return "Boolean";
        }
        if (type.contains("bigint")) {
            return "Long";
        }
        if (type.contains("int")) {
//            int length = Integer.parseInt(type.substring(type.indexOf("(") + 1, type.indexOf(")")));
//            return length > 10 ? "long" : "int";
            return "Integer";
        }
        if (type.contains("decimal")) {
            return "java.math.BigDecimal";
        }

        if (type.contains("double")) {
            return "Double";
        }
        if (type.contains("float")) {
            return "Float";
        }
        if (type.contains("datetime") || type.contains("timestamp")) {
            return "Date";
        }
        throw new IllegalArgumentException("未处理的数据库类型：" + name + "," + type);
    }

    private void line(String line, int tabs, StringBuffer sb) {
        for (int i = 0; i < tabs; i++) {
            sb.append("   ");
        }
        sb.append(line);
        sb.append("\n");
    }

    private static String upper(String table) {
        String[] words = table.split("_");
        String re = "";
        for (String word : words) {
            re += Character.toUpperCase(word.charAt(0)) + word.substring(1);
        }
        return re;
    }

    private static String upperForColumn(String column) {
        String[] words = column.split("_");
        String re = "";
        for (int i = 0; i < words.length; i++) {
            if (i == 0) {
                re += Character.toLowerCase(words[i].charAt(0))
                        + words[i].substring(1);
            } else {

                re += Character.toUpperCase(words[i].charAt(0)) +
                        words[i].substring(1);
            }
        }
        return re;
    }


    private static String lowerFirstChar(String item) {
        String substring = item.substring(1);
        String first = item.substring(0, 1).toLowerCase();
        return first + substring;
    }


}
